package com.wimcorp.magic.players;

import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;

import javax.transaction.SystemException;

import org.apache.commons.lang.StringUtils;

import com.wimcorp.magic.abilities.Ability;
import com.wimcorp.magic.behaviour.DoesNotUnTapNextTurn;
import com.wimcorp.magic.behaviour.DoesNotUnTapOnUpkeep;
import com.wimcorp.magic.behaviour.Tapped;
import com.wimcorp.magic.board.CardType;
import com.wimcorp.magic.board.Hand;
import com.wimcorp.magic.board.PlayingDeck;
import com.wimcorp.magic.board.SummoningPlains;
import com.wimcorp.magic.cards.BasicLand;
import com.wimcorp.magic.cards.Creature;
import com.wimcorp.magic.cards.Enchantment;
import com.wimcorp.magic.cards.Instant;
import com.wimcorp.magic.cards.Land;
import com.wimcorp.magic.cards.PlayingCard;
import com.wimcorp.magic.cards.Sorcery;
import com.wimcorp.magic.color.Color;
import com.wimcorp.magic.combat.Target;
import com.wimcorp.magic.cost.Payment;
import com.wimcorp.magic.exception.AttackException;
import com.wimcorp.magic.exception.BlockerException;
import com.wimcorp.magic.exception.CostException;
import com.wimcorp.magic.exception.ManaException;
import com.wimcorp.magic.mana.Mana;
import com.wimcorp.magic.mana.ManaPool;
import com.wimcorp.magic.mana.PlayerManaPool;
import com.wimcorp.magic.phase.BeginningPhase;
import com.wimcorp.magic.phase.CombatPhase;
import com.wimcorp.magic.phase.EndingPhase;
import com.wimcorp.magic.phase.MainPhase;
import com.wimcorp.magic.phase.Turn;
import com.wimcorp.magic.spell.Castable;
import com.wimcorp.magic.stack.Stack;
import com.wimcorp.magic.userinterface.UserInterface;

public class Player implements Target {

	private String name;

	private int life = 20;

	private PlayingDeck deck;

	private Hand hand = new Hand(this);

	private SummoningPlains summoningPlaines;

	private Payment payment = new Payment();

	private PlayerManaPool playerManaPool = new PlayerManaPool();

	private Player opponent;

	private Stack stack;

	private BeginningPhase beginningPhase;

	private MainPhase mainPhase;

	private CombatPhase combatPhase;

	private EndingPhase endingPhase;

	private UserInterface userInterface;

	private boolean ready;

	private int mulliganCounter = 7;

	public Player(String name, UserInterface userInterface, Stack stack) {
		this.name = name;
		this.userInterface = userInterface;
		this.stack = stack;
		summoningPlaines = new SummoningPlains(this);
		beginningPhase = new BeginningPhase(this);
		mainPhase = new MainPhase(this);
		combatPhase = new CombatPhase(this);
		endingPhase = new EndingPhase(this);
	}

	public int untapCards() {
		int count = 0;
		Collection<PlayingCard> cards = summoningPlaines.getCards(CardType.ALL);
		for (PlayingCard card : cards) {
			if (card.hasBehaviour(Tapped.instance()) && !card.hasBehaviour(DoesNotUnTapNextTurn.instance())
					&& !card.hasBehaviour(DoesNotUnTapOnUpkeep.instance())) {
				card.removeBehaviour(Tapped.instance());
				count++;
			}
			if (card.hasBehaviour(DoesNotUnTapNextTurn.instance())) {
				card.removeBehaviour(DoesNotUnTapNextTurn.instance());
			}
		}
		return count;
	}

	public PlayingCard drawCard() {
		PlayingCard card = deck.getTopCard();
		hand.add(card);
		return card;
	}

	public void discardCard(PlayingCard card) {
		summoningPlaines.getGraveyard().add(card);
		hand.remove(card);
	}

	/*
	 * public void cast(Castable castable) { if (castable instanceof
	 * PlayingCard) { try { cast((PlayingCard) castable); } catch (CostException
	 * e) { message(e.getMessage()); } } else if (castable instanceof Ability) {
	 * cast((Ability) castable); } }
	 */

	public void cast(PlayingCard card) throws CostException {
		card.setPlayer(this);
		card.payCost();
		stack.addOnStack(card);
		hand.remove(card);
		message(String.format("You played %s.", card.getName()));
		for (Ability ability : card.getAbilities()) {
			try {
				ability.configure();
				if (card instanceof Enchantment) {
					PlayingCard target = ability.getAbilityTarget();
					if(target != null && target instanceof Creature){
						((Creature)target).addEnchantment((Enchantment)card);
					}
				}else if (card instanceof Instant) {
					PlayingCard target = ability.getAbilityTarget();
					if(target != null && target instanceof Creature){
						((Creature)target).addInstant((Instant)card);
					}
				}else if (card instanceof Sorcery) {
					PlayingCard target = ability.getAbilityTarget();
					if(target != null && target instanceof Creature){
						((Creature)target).addSorcery((Sorcery)card);
					}
				}
			} catch (RemoteException e) {
				e.printStackTrace();
			}
		}
	}

	public void cast(Ability ability) {
		try {
			ability.payCost();
			stack.addOnStack(ability);
		} catch (CostException e) {
			try {
				getUserInterface().message(e.getMessage());
			} catch (RemoteException e1) {
				e1.printStackTrace();
			}
		}

	}

	public boolean castLand(PlayingCard card) throws SystemException, CostException {
		if (card instanceof Land) {
			card.setPlayer(this);
			card.payCost();
			card.cast();
			hand.remove(card);
			return true;
		} else {
			throw new SystemException(card.getName() + " is not a land");
		}

	}

	public void castFromDeck() {
		PlayingCard card = deck.getTopCard();
		try {
			card.setPlayer(this);
			card.payCost();
			stack.addOnStack(card);
		} catch (Exception e) {
			try {
				getUserInterface().message(e.getMessage());
			} catch (RemoteException e1) {
				e1.printStackTrace();
			}
			getDeck().addOnTop(card);
		}
	}

	public void castFromExileWithoutCost(PlayingCard card) {
		card.setPlayer(this);
		stack.addOnStack(card);
		getSummoningPlaines().getExile().remove(card);
	}

	public void declareAttackers(Player targetPlayer, Creature... creatures) {
		List<Creature> attackers = new ArrayList<>();
		for (Creature creature : creatures) {
			attackers.add(creature);
		}
		declareAttackers(targetPlayer, attackers);
	}

	public void declareAttackers(Player targetPlayer, List<Creature> creatures) {
		for (Creature creature : creatures) {
			try {
				creature.declareAttack(targetPlayer);
				message(String.format("You declare attack with %s", creature.getName()));
				messageOpponent(String.format("Opponent declares attack with %s", creature.getName()));
			} catch (AttackException e1) {
				message(String.format("%s %s", creature.getName(), e1.getMessage()));
			}
		}
	}

	public void declareBlocker(Creature attacker, Creature... creatures) throws BlockerException {
		List<Creature> blockers = new ArrayList<>();
		for (Creature creature : creatures) {
			blockers.add(creature);
		}
		declareBlocker(attacker, blockers);
	}

	public void declareBlocker(Creature attacker, List<Creature> blockers) throws BlockerException {
		StringBuilder defenders = new StringBuilder();
		int i = 1;
		for (Creature blocker : blockers) {
			blocker.declareBlock(attacker);
			defenders.append(blocker.getName());
			if (i != blockers.size()) {
				defenders.append(", ");
			}
			i++;
		}
		try {
			if (StringUtils.isNotEmpty(defenders.toString())) {
				message(String.format("You block attacker %s with %s. ", attacker.getName(), defenders.toString()));
				getOpponent().getUserInterface()
						.message(
								String.format("Opponent blocks attacker %s with %s. ", attacker.getName(),
										defenders.toString()));
			}
		} catch (RemoteException e) {
			e.printStackTrace();
		}
	}

	public ManaPool conjureMana(Color... colors) {
		for (Color color : colors) {
			message("You conjured a " + color.getColor() + " mana. ");
			messageOpponent("Your opponent conjured a " + color.getColor() + " mana. ");
		}
		return payment.conjureMana(colors);
	}

	public ManaPool conjureMana(BasicLand... lands) {
		return payment.conjureMana(lands);
	}

	public void unConjureMana(BasicLand card) throws ManaException {
		List<Mana> mana = payment.getConjuredMana().getMana(card.getColor());
		if (mana != null && mana.size() > 0) {
			mana.remove(mana.size() - 1);
			card.removeBehaviour(Tapped.instance());
		} else {
			throw new ManaException(String.format("You have no mana of %s in your manapool %s.", card.getColor(),
					payment.getConjuredMana()));
		}
	}

	public void consumeMana() throws CostException {
		playerManaPool.consumeMana(payment.getConjuredMana());
		clearPayment();
	}

	public void clearPayment() {
		payment = new Payment();

	}

	public void resolveAttack(Player targetPlayer) {
		Set<Creature> attackers = getSummoningPlaines().getAttackersOnPlayer(targetPlayer);
		if (attackers != null) {
			for (PlayingCard attacker : attackers) {
				Creature attackingCreature = (Creature) attacker;
				// attackingCreature.resolveCreature();
				if (attackingCreature.isAlive()) {
					Set<Creature> blockers = getSummoningPlaines().getBlockers((Creature) attacker);
					if (blockers == null) {
						attackingCreature.attack(targetPlayer);
					} else {
						for (Creature blocker : blockers) {
							// blocker.resolveCreature();
							if (blocker.isAlive()) {
								attackingCreature.attack(blocker);
							} else {
								attackingCreature.attack(blocker.getPlayer());
							}
						}
					}
				}
			}
		}
	}

	public void addLife(int amount) {
		life += amount;
	}

	public void subtractLife(int amount) {
		life -= amount;
	}

	public void removeCreatureDamage() {
		Collection<PlayingCard> cards = summoningPlaines.getCards(CardType.ALL);
		for (PlayingCard card : cards) {
			if (card instanceof Creature) {
				((Creature) card).resolveCreature();
			}
		}
	}

	public void play() {
		beginningPhase.execute();
		mainPhase.execute();
		combatPhase.execute();
		Turn.setSecondMainPhase(true);
		mainPhase.execute();
		endingPhase.execute();
		Turn.reset();
		if (Turn.isFirstTurn()) {
			Turn.setFirstTurn(false);
		}

	}

	public void message(String message) {
		try {
			userInterface.message(message);
		} catch (RemoteException e) {
			e.printStackTrace();
		}
	}

	public void messageOpponent(String message) {
		try {
			getOpponent().getUserInterface().message(message);
		} catch (RemoteException e) {
			e.printStackTrace();
		}

	}

	/*
	 * public static Collection<Player> getAllPlayers() { return
	 * players.values(); }
	 * 
	 * public static Player getPlayer(String name) { return players.get(name); }
	 */

	public int mulligan() {
		deck.addOnBottom(hand.getCards());
		deck.shuffle();
		setHand(new Hand(this, deck.drawCards(mulliganCounter)));
		if (mulliganCounter != 0) {
			mulliganCounter--;
			return mulliganCounter + 1;
		} else {
			return 0;
		}

	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public PlayingDeck getDeck() {
		return deck;
	}

	public void setDeck(PlayingDeck deck) {
		this.deck = deck;
	}

	public Hand getHand() {
		return hand;
	}

	public void setHand(Hand hand) {
		this.hand = hand;
	}

	public SummoningPlains getSummoningPlaines() {
		return summoningPlaines;
	}

	public void setSummoningPlaines(SummoningPlains summoningPlaines) {
		this.summoningPlaines = summoningPlaines;
	}

	public PlayerManaPool getPlayerManaPool() {
		return playerManaPool;
	}

	public Payment getPayment() {
		return payment;
	}

	public UserInterface getUserInterface() {
		return userInterface;
	}

	public boolean isReady() {
		return ready;
	}

	public void setReady(boolean ready) {
		this.ready = ready;
	}

	public int getLife() {
		return life;
	}

	public BeginningPhase getBeginningPhase() {
		return beginningPhase;
	}

	public MainPhase getMainPhase() {
		return mainPhase;
	}

	public CombatPhase getCombatPhase() {
		return combatPhase;
	}

	public EndingPhase getEndingPhase() {
		return endingPhase;
	}

	public Stack getStack() {
		return stack;
	}

	public Player getOpponent() {
		return opponent;
	}

	public void setOpponent(Player opponent) {
		this.opponent = opponent;
	}

}
